package ru.niir.goodfil.ui;

import ru.niir.goodfil.exceptions.GoodfilSecurityException;
import ru.niir.goodfil.exceptions.SettingNotFoundException;
import ru.niir.goodfil.db.*;
import ru.niir.goodfil.db.handlers.Goodfil;

import java.util.*;
import java.text.SimpleDateFormat;

import org.apache.log4j.Logger;

import javax.faces.model.SelectItem;

/**
 * Бин для формирования отчета о клиентских группах.
 * Отчет позволяет получить прогнозируемую клиентскую группу
 * для клиента на основе его закупок за некоторый интервал.
 */
public class ClientGroupsStatBean extends PageBean
{
    private final static Logger logger = Logger.getLogger(ClientGroupsStatBean.class);

    /**
     * Тип отчета - это период, за который может формироваться отчет.
     * Задается константами REPORT_TYPE_CALENDAR_MONTH, REPORT_TYPE_MONTH и REPORT_TYPE_MONTH_BEGIN
     */
    private int reportType = 0;

    /**
     * Идентификатор клиента. Если -1 то отчет формируется для всех клиентов.
     * Если отчет формируется для всех клиентов, то в таблицу выводятся только клиенты, прогнозируемая группа которых не совпадает с текущей.
     * Если отчет формируется для конкретного клиента, он (и только он) попадает в таблицу даже если его текущая группа совпадает с прогнозируемой.
     */
    private long clientId;

    /**
     * Идентификатор брэнда, по которому строится отчет
     */
    private long brandId;

    /**
     * Конструктор.
     * К отчету допускаются только администраторы.
     */
    public ClientGroupsStatBean()
    {
        if (!getSecurityBean().getIsRoleAdministrator())
            throw new GoodfilSecurityException("Функция \"определение клиентской группы по результатам предыдущего месяца\" доступна только администраторам");
    }

    /**
     * Процедура, формирующая данные для отчета
     */
    public List<ClientGroupStatInfo> getClientGroupStatInfos() throws SettingNotFoundException
    {
        //  Если тип отчета не выбран, ничего возвращать не надо
        if (reportType == 0) return new ArrayList<ClientGroupStatInfo>();

        //  Если брэнд не выбран, то ничего возвращать не надо
        if (brandId <= 0) return new ArrayList<ClientGroupStatInfo>();

        Calendar c = Calendar.getInstance();

        //  Текущие часы/минуты/секунды/миллисекунды не учитываются
        c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0);

        Date begDate = c.getTime(); //  Дата начала отчетного периода
        Date endDate = c.getTime(); //  Дата окончания отчетного периода

        if (reportType == REPORT_TYPE_MONTH_BEGIN)
        {   //  Формируем отчет с начала месяца по сегодняшнее число
            endDate = c.getTime();
            c.set(Calendar.DAY_OF_MONTH, 1);
            begDate = c.getTime();
        }
        else if (reportType == REPORT_TYPE_MONTH)
        {   //  Формируем отчет с начала предыдущего месяца по конец предыдущего месяца
            c.set(Calendar.DAY_OF_MONTH, 1);
            endDate = c.getTime();
            c.add(Calendar.MONTH, -1);
            begDate = c.getTime();
        }
        else if (reportType == REPORT_TYPE_CALENDAR_MONTH)
        {   //  Формируем отчет с (сегодняшнее число минус месяц) по сегодняшнее число
            endDate = c.getTime();
            c.add(Calendar.MONTH, -1);
            begDate = c.getTime();
        }

        //  Выводим в логи получившееся даты
        SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy HH:mm");
        long minTime = begDate.getTime();
        long maxTime = endDate.getTime();
        logger.debug("Min order creationDate: " + sdf.format(begDate) + " (" + minTime + ")");
        logger.debug("Max order creationDate: " + sdf.format(endDate) + " (" + maxTime + ")");

        //  Кэшируем перечень клиентских групп
        List<ClientGroup> clientGroups = gf().getClientGroupsByBrandId(brandId);

        //  Массив выходных данных
        List<ClientGroupStatInfo> result = new ArrayList<ClientGroupStatInfo>();

        //  Получаем список клиентов для обработки. Это либо все клиенты, либо один конкретный
        //  в зависимости от переменной clientId
        List<Client> clients = new ArrayList<Client>();
        if (clientId == -1) clients.addAll(gf().getClients(0, Integer.MAX_VALUE, null));
        else clients.add(gf().getClientById(clientId));

        for (Client client : clients)
        {
            logger.debug("Client: " + client.getCompanyName() + " [" + client.getId() + "]");

            //  Получаем текущую клиентскую группу
            //  long currentClientGroupId = client.getClientGroupId();
            long currentClientGroupId = client.getClientgroupIdByBrandId(brandId);

            //  Получаем рекомендуемую клиентскую группу и сумму закупок
            Goodfil.CalcClientGroupResult r = gf().calculateClientGroupByClientId(client.getId(), brandId, begDate, endDate);
            long targetClientGroupId = r.getClientGroupId();

            if (currentClientGroupId == targetClientGroupId && clientId == -1) continue;

            ClientGroup currentClientGroup = null;
            ClientGroup targetClientGroup = null;
            for (ClientGroup clientGroup : clientGroups)
            {
                if (clientGroup.getId() == currentClientGroupId) currentClientGroup = clientGroup;
                if (clientGroup.getId() == targetClientGroupId) targetClientGroup = clientGroup;
            }

            if (client.getManagerId() != null) client.setManager(gf().getUserById(client.getManagerId()));

            result.add(new ClientGroupStatInfo(client, currentClientGroup, targetClientGroup, r.getSum()));
        }

        logger.debug("Items in report: " + result.size());
        return result;
    }

   /**
     * Тип отчета - это период, за который может формироваться отчет.
     * Задается константами REPORT_TYPE_CALENDAR_MONTH, REPORT_TYPE_MONTH и REPORT_TYPE_MONTH_BEGIN
     */
    public void setReportType(int reportType)
    {
        this.reportType = reportType;
    }

    /**
     * Тип отчета - это период, за который может формироваться отчет.
     * Задается константами REPORT_TYPE_CALENDAR_MONTH, REPORT_TYPE_MONTH и REPORT_TYPE_MONTH_BEGIN
     */
    public int getReportType()
    {
        return reportType;
    }

    /**
     * Формируем отчет с начала месяца по сегодняшнее число
     */
    public static final int REPORT_TYPE_MONTH_BEGIN = 1;

    /**
     * Формируем отчет с начала предыдущего месяца по конец предыдущего месяца
     */
    public static final int REPORT_TYPE_MONTH = 2;

    /**
     * Формируем отчет с (сегодняшнее число минус месяц) по сегодняшнее число
     */
    public static final int REPORT_TYPE_CALENDAR_MONTH = 3;

    /**
     * Перечень типов отчета для отображения на странице
     */
    public List<SelectItem> getReportTypes()
    {
        List<SelectItem> result = new ArrayList<SelectItem>();
        result.add(new SelectItem(REPORT_TYPE_MONTH_BEGIN, "Начиная с начала текущего месяца"));
        result.add(new SelectItem(REPORT_TYPE_MONTH, "За предыдущий месяц"));
        result.add(new SelectItem(REPORT_TYPE_CALENDAR_MONTH, "За календарный месяц"));
        return result;
    }

    /**
     * Получить перечень клиентов
     */
    public List<SelectItem> getClientsAsSelectItems()
    {
        List<SelectItem> selectItems = new LinkedList<SelectItem>();
        List<ClientHeader> clientHeders = gf().getClientHeaders();
        for (ClientHeader clientHeader : clientHeders)
            selectItems.add(new SelectItem(clientHeader.getClientId(), clientHeader.getName()));
        return selectItems;
    }

    /**
     * Идентификатор клиента. Если -1 то отчет формируется для всех клиентов.
     * Если отчет формируется для всех клиентов, то в таблицу выводятся только клиенты, прогнозируемая группа которых не совпадает с текущей.
     * Если отчет формируется для конкретного клиента, он (и только он) попадает в таблицу даже если его текущая группа совпадает с прогнозируемой.
     */
    public void setClientId(long clientId)
    {
        this.clientId = clientId;
    }

    /**
     * Идентификатор клиента. Если -1 то отчет формируется для всех клиентов.
     * Если отчет формируется для всех клиентов, то в таблицу выводятся только клиенты, прогнозируемая группа которых не совпадает с текущей.
     * Если отчет формируется для конкретного клиента, он (и только он) попадает в таблицу даже если его текущая группа совпадает с прогнозируемой.
     */
    public long getClientId()
    {
        return clientId;
    }

    /**
     * Идентификатор брэнда, по которому формируется отчет
     */
    public long getBrandId()
    {
        return brandId;
    }

    /**
     * Идентификатор брэнда, по которому формируется отчет
     */
    public void setBrandId(long brandId)
    {
        this.brandId = brandId;
    }

    /**
     * Класс для хранения данных по отчету для каждого клиента
     */
    public static class ClientGroupStatInfo
    {
        /**
         * Конструктор
         * @param client клиент
         * @param currentGroup текущая клиентская группа
         * @param targetGroup рекомендуемая клиентская группа
         * @param sum сумма закупок клиента за отчетный период
         */
        public ClientGroupStatInfo(Client client, ClientGroup currentGroup, ClientGroup targetGroup, float sum)
        {
            this.client = client;
            this.currentGroup = currentGroup;
            this.targetGroup = targetGroup;
            this.sum = sum;
        }

        /**
         * Cумма закупок клиента за отчетный период
         */
        private float sum;

        /**
         * Клиент
         */
        private Client client;

        /**
         * Текущая клиентская группа
         */
        private ClientGroup currentGroup;

        /**
         * Рекомендуемая клиентская группа
         */
        private ClientGroup targetGroup;

        /**
         * Клиент
         */
        public Client getClient()
        {
            return client;
        }

        /**
         * Клиент
         */
        public void setClient(Client client)
        {
            this.client = client;
        }

        /**
         * Текущая клиентская группа
         */
        public ClientGroup getCurrentGroup()
        {
            return currentGroup;
        }

        /**
         * Текущая клиентская группа
         */
        public void setCurrentGroup(ClientGroup currentGroup)
        {
            this.currentGroup = currentGroup;
        }

        /**
         * Рекомендуемая клиентская группа
         */
        public ClientGroup getTargetGroup()
        {
            return targetGroup;
        }

        /**
         * Рекомендуемая клиентская группа
         */
        public void setTargetGroup(ClientGroup targetGroup)
        {
            this.targetGroup = targetGroup;
        }

        /**
         * Cумма закупок клиента за отчетный период
         */
        public float getSum()
        {
            return sum;
        }

        /**
         * Cумма закупок клиента за отчетный период
         */
        public void setSum(float sum)
        {
            this.sum = sum;
        }
    }
}
